﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace Compiler
{
    public class LR0Item
    {
        public Production production;
        private readonly int dotpos;

        //S -> abc 
        //Items: S -> .abc    S -> a.bc     S -> ab.c    S -> abc. 
        //=> der punkt ist vor dem dotpos-ten tail-symbol der produktion (nullbasiert)
        public LR0Item(Production prod, int dotpos)
        {
            if (dotpos < 0 || dotpos > prod.Tail.Count || prod.isEpsilonProduction() && dotpos > 0)
                throw new ArgumentException();

            this.production = prod;
            
            this.dotpos = dotpos;
          //todo:beachten  if (prod.isEpsilonProduction())
          //      this.dotpos = -100;
        }

        //public static List<LR0Item> CreateAll(Production prod)
        //{
        //    int itemCount = prod.isEpsilonProduction() ? 1 : prod.Tail.Count + 1;

        //    return Enumerable.Range(0, itemCount).Select(i => new LR0Item(prod, i)).ToList();
        //}

        public static LR0Item CreateItem_MoveDotForwad(LR0Item item)
        {
            if (item.dotpos == item.production.Tail.Count || item.production.isEpsilonProduction())
                throw new ArgumentException("geht net!");

            return new LR0Item(item.production, item.dotpos + 1);
        }

        public static LR0Item CreateItem_DotAtStart(Production prod)
        {
            return new LR0Item(prod, 0);
        }

        public static LR0Item CreateItem_DotAtEnd(Production prod)
        {
            if (prod.isEpsilonProduction())
                throw new Exception();
            return new LR0Item(prod, prod.Tail.Count);
        }

        public static HashSet<LR0Item> Goto0(Grammar grammar, HashSet<LR0Item> I, Symbol X)
        {
            LR0ItemEqualityComparer lr0Cmp = new LR0ItemEqualityComparer();
            HashSet<LR0Item> final = new HashSet<LR0Item>(lr0Cmp);

            foreach (LR0Item item in I)
            {
                if (item.IsSymbolOnRightside(X))
                    final.Add(CreateItem_MoveDotForwad(item));
            }

            return Closure0(grammar, final);
        }

        public static HashSet<LR0Item> Closure0(Grammar grammar, HashSet<LR0Item> itemSet)
        {
            LR0ItemEqualityComparer lr0Cmp = new LR0ItemEqualityComparer();
            HashSet<LR0Item> final = new HashSet<LR0Item>(itemSet, lr0Cmp);
            HashSet<LR0Item> working = new HashSet<LR0Item>(itemSet, lr0Cmp);

            HashSet<Nonterminal> doneSet = new HashSet<Nonterminal>(new Nonterminal.NonterminalEqualityComparer());

            do
            {
                HashSet<LR0Item> newItems = new HashSet<LR0Item>(lr0Cmp);
                
                foreach (LR0Item item in working)
                {
                    Nonterminal nt;
                    //hat das LR0-Item ein nichtterminal rechts vom punkt?
                    if (item.NonterminalOnDotsRightside(out nt))
                    {   
                        //dieses nonterminal wurde bisher noch nicht gefunden
                        if (!doneSet.Contains(nt))
                        {
                            doneSet.Add(nt);
                            List<Production> prodForMeta = grammar.Productions[nt]; //grammar.getProductionsForNonterminal(nt);
                            List<LR0Item> lr0ItemsForMeta = prodForMeta.Select(prod => CreateItem_DotAtStart(prod)).ToList();
                            newItems.UnionWith(lr0ItemsForMeta);
                        }
                    }
                }

                final.UnionWith(newItems);
                working = newItems;

            } while (working.Count > 0);

            return final;
        }

        public bool IsItemWithDotFarRight()
        {
            return production.isEpsilonProduction() || dotpos == production.Tail.Count;
        }

        public bool NonterminalOnDotsRightside(out Nonterminal nonterminal)
        {
            nonterminal = null;
            if (dotpos == production.Tail.Count || production.isEpsilonProduction())
                return false;

            nonterminal = production.Tail[dotpos] as Nonterminal;
            return nonterminal != null;
        }

        public bool NonterminalOnDotsRightside(out Nonterminal nonterminal, out List<Symbol> restChain)
        {
            nonterminal = null;
            restChain = null;
            if (dotpos == production.Tail.Count || production.isEpsilonProduction())
                return false;

            nonterminal = production.Tail[dotpos] as Nonterminal;
            if (nonterminal != null)
                restChain = production.Tail.Skip(dotpos + 1).ToList();
            return nonterminal != null;
        }

        //unklar was bei epsilon-produktion zu tun ist.
        public bool IsSymbolOnRightside(Symbol symbol)
        {
            //if (symbol == Symbol.Epsilon)
            //    throw new NotImplementedException("weiß nicht wie");
            if (symbol == Symbol.Epsilon)
                return false;

            if (dotpos == production.Tail.Count)
                return false;

            if (production.isEpsilonProduction())
                return false;

            Symbol.SymbolEqualityComparer symCmp = new Symbol.SymbolEqualityComparer();
            return symCmp.Equals(production.Tail[dotpos], symbol);
        }

        public Symbol GetSymbolOnRightside()
        {
            if (dotpos == production.Tail.Count)// || production.isEpsilonProduction())
                return null;

            if (production.isEpsilonProduction())
                return null;

            if (production.Tail[dotpos] == Symbol.Epsilon)
                return null;// throw new NotImplementedException("was tun?");

            return production.Tail[dotpos];
        }

        public override string ToString()
        {
            if (production.isEpsilonProduction())
                return string.Format("{0} => .", production.Head.Name);

            string tail = "";
            for (int i = 0; i < production.Tail.Count; ++i)
            {
                if (i == dotpos)
                    tail += ".";
                tail += production.Tail[i];
            }
            if (dotpos == production.Tail.Count)
                tail += ".";

            return string.Format("{0} => {1}", production.Head.Name, tail);
        }

     
        public class LR0ItemEqualityComparer : IEqualityComparer<LR0Item>
        {
            Production.ProductionEqualityComparer prodCmp = new Production.ProductionEqualityComparer();

            public bool Equals(LR0Item x, LR0Item y)
            {
                return prodCmp.Equals(x.production, y.production) && (
                        x.dotpos == y.dotpos || x.production.isEpsilonProduction() && y.production.isEpsilonProduction());
            }

            //Equals(x,y) => hash(x) == hash(y)
            public int GetHashCode(LR0Item obj)
            {
                //return obj.dotpos.GetHashCode() + 17 * prodCmp.GetHashCode(obj.production);
                return prodCmp.GetHashCode(obj.production);
            }
        }
    }
}
